2 ==============================================================================
\r
4 This file is part of the JUCE library.
\r
5 Copyright (c) 2017 - ROLI Ltd.
\r
7 JUCE is an open source library subject to commercial or open-source
\r
10 The code included in this file is provided under the terms of the ISC license
\r
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
\r
12 To use, copy, modify, and/or distribute this software for any purpose with or
\r
13 without fee is hereby granted provided that the above copyright notice and
\r
14 this permission notice appear in all copies.
\r
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
\r
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
\r
20 ==============================================================================
\r
23 package com.roli.juce.pluginhost;
\r
25 import android.app.Activity;
\r
26 import android.app.AlertDialog;
\r
27 import android.content.DialogInterface;
\r
28 import android.content.Context;
\r
29 import android.content.Intent;
\r
30 import android.content.res.Configuration;
\r
31 import android.content.pm.PackageInfo;
\r
32 import android.content.pm.PackageManager;
\r
33 import android.hardware.camera2.*;
\r
34 import android.database.ContentObserver;
\r
35 import android.media.session.*;
\r
36 import android.media.MediaMetadata;
\r
37 import android.net.http.SslError;
\r
38 import android.net.Uri;
\r
39 import android.os.Bundle;
\r
40 import android.os.Looper;
\r
41 import android.os.Handler;
\r
42 import android.os.Message;
\r
43 import android.os.ParcelUuid;
\r
44 import android.os.Environment;
\r
45 import android.view.*;
\r
46 import android.view.inputmethod.BaseInputConnection;
\r
47 import android.view.inputmethod.EditorInfo;
\r
48 import android.view.inputmethod.InputConnection;
\r
49 import android.view.inputmethod.InputMethodManager;
\r
50 import android.graphics.*;
\r
51 import android.text.ClipboardManager;
\r
52 import android.text.InputType;
\r
53 import android.util.DisplayMetrics;
\r
54 import android.util.Log;
\r
55 import android.util.Pair;
\r
56 import android.webkit.SslErrorHandler;
\r
57 import android.webkit.WebChromeClient;
\r
58 import android.webkit.WebResourceError;
\r
59 import android.webkit.WebResourceRequest;
\r
60 import android.webkit.WebResourceResponse;
\r
61 import android.webkit.WebView;
\r
62 import android.webkit.WebViewClient;
\r
63 import java.lang.Runnable;
\r
64 import java.lang.ref.WeakReference;
\r
65 import java.lang.reflect.*;
\r
68 import java.net.URL;
\r
69 import java.net.HttpURLConnection;
\r
70 import android.media.AudioManager;
\r
71 import android.Manifest;
\r
72 import java.util.concurrent.CancellationException;
\r
73 import java.util.concurrent.Future;
\r
74 import java.util.concurrent.Executors;
\r
75 import java.util.concurrent.ExecutorService;
\r
76 import java.util.concurrent.ExecutionException;
\r
77 import java.util.concurrent.TimeUnit;
\r
78 import java.util.concurrent.Callable;
\r
79 import java.util.concurrent.TimeoutException;
\r
80 import java.util.concurrent.locks.ReentrantLock;
\r
81 import java.util.concurrent.atomic.*;
\r
83 import android.media.midi.*;
\r
84 import android.bluetooth.*;
\r
85 import android.bluetooth.le.*;
\r
88 //==============================================================================
\r
89 public class AudioPluginHost extends Activity
\r
91 //==============================================================================
\r
94 System.loadLibrary ("juce_jni");
\r
97 //==============================================================================
\r
98 public boolean isPermissionDeclaredInManifest (int permissionID)
\r
100 return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
\r
103 public boolean isPermissionDeclaredInManifest (String permissionToCheck)
\r
107 PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
\r
109 if (info.requestedPermissions != null)
\r
110 for (String permission : info.requestedPermissions)
\r
111 if (permission.equals (permissionToCheck))
\r
114 catch (PackageManager.NameNotFoundException e)
\r
116 Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
\r
119 Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
\r
123 //==============================================================================
\r
124 // these have to match the values of enum PermissionID in C++ class RuntimePermissions:
\r
125 private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
\r
126 private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
\r
127 private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
\r
128 private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
\r
129 private static final int JUCE_PERMISSIONS_CAMERA = 5;
\r
131 private static String getAndroidPermissionName (int permissionID)
\r
133 switch (permissionID)
\r
135 case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
\r
136 case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
\r
137 // use string value as this is not defined in SDKs < 16
\r
138 case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
\r
139 case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
\r
140 case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
\r
143 // unknown permission ID!
\r
145 return new String();
\r
148 public boolean isPermissionGranted (int permissionID)
\r
150 return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
\r
153 private Map<Integer, Long> permissionCallbackPtrMap;
\r
155 public void requestRuntimePermission (int permissionID, long ptrToCallback)
\r
157 String permissionName = getAndroidPermissionName (permissionID);
\r
159 if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)
\r
161 // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
\r
162 permissionCallbackPtrMap.put (permissionID, ptrToCallback);
\r
163 requestPermissionsCompat (new String[]{permissionName}, permissionID);
\r
167 // permissions were already granted before, we can call callback directly
\r
168 androidRuntimePermissionsCallback (true, ptrToCallback);
\r
172 private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
\r
175 public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
\r
177 boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);
\r
179 if (! permissionsGranted)
\r
180 Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));
\r
182 Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);
\r
183 permissionCallbackPtrMap.remove (permissionID);
\r
184 androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);
\r
187 //==============================================================================
\r
188 public interface JuceMidiPort
\r
190 boolean isInputPort();
\r
192 // start, stop does nothing on an output port
\r
198 // send will do nothing on an input port
\r
199 void sendMidi (byte[] msg, int offset, int count);
\r
202 //==============================================================================
\r
203 //==============================================================================
\r
204 public class BluetoothManager extends ScanCallback
\r
210 public String[] getMidiBluetoothAddresses()
\r
212 return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
\r
215 public String getHumanReadableStringForBluetoothAddress (String address)
\r
217 BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
\r
218 return btDevice.getName();
\r
221 public int getBluetoothDeviceStatus (String address)
\r
223 return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);
\r
226 public void startStopScan (boolean shouldStart)
\r
228 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
\r
230 if (bluetoothAdapter == null)
\r
232 Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
\r
236 BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
\r
238 if (bluetoothLeScanner == null)
\r
240 Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
\r
246 ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
\r
247 scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
\r
249 ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
\r
250 scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
\r
251 .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
\r
252 .setScanMode (ScanSettings.MATCH_MODE_STICKY);
\r
254 bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
\r
255 scanSettingsBuilder.build(),
\r
260 bluetoothLeScanner.stopScan (this);
\r
264 public boolean pairBluetoothMidiDevice(String address)
\r
266 BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
\r
268 if (btDevice == null)
\r
270 Log.d ("JUCE", "failed to create buletooth device from address");
\r
274 return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);
\r
277 public void unpairBluetoothMidiDevice (String address)
\r
279 getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
\r
282 public void onScanFailed (int errorCode)
\r
286 public void onScanResult (int callbackType, ScanResult result)
\r
288 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
\r
289 || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
\r
291 BluetoothDevice device = result.getDevice();
\r
293 if (device != null)
\r
294 bluetoothMidiDevices.add (device.getAddress());
\r
297 if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
\r
299 Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
\r
300 BluetoothDevice device = result.getDevice();
\r
302 if (device != null)
\r
304 bluetoothMidiDevices.remove (device.getAddress());
\r
305 unpairBluetoothMidiDevice (device.getAddress());
\r
310 public void onBatchScanResults (List<ScanResult> results)
\r
312 for (ScanResult result : results)
\r
313 onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
\r
316 private BluetoothLeScanner scanner;
\r
317 private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
\r
319 private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
\r
322 public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
\r
324 private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
\r
326 public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)
\r
329 androidPort = actualPort;
\r
330 portPath = portPathToUse;
\r
331 juceHost = hostToUse;
\r
332 isConnected = false;
\r
336 protected void finalize() throws Throwable
\r
343 public boolean isInputPort()
\r
349 public void start()
\r
351 if (owner != null && androidPort != null && ! isConnected) {
\r
352 androidPort.connect(this);
\r
353 isConnected = true;
\r
360 if (owner != null && androidPort != null && isConnected) {
\r
361 androidPort.disconnect(this);
\r
362 isConnected = false;
\r
367 public void close()
\r
369 if (androidPort != null) {
\r
371 androidPort.close();
\r
372 } catch (IOException exception) {
\r
373 Log.d("JUCE", "IO Exception while closing port");
\r
378 owner.removePort (portPath);
\r
381 androidPort = null;
\r
385 public void onSend (byte[] msg, int offset, int count, long timestamp)
\r
388 handleReceive (juceHost, msg, offset, count, timestamp);
\r
392 public void onFlush()
\r
396 public void sendMidi (byte[] msg, int offset, int count)
\r
400 MidiDeviceManager owner;
\r
401 MidiOutputPort androidPort;
\r
402 MidiPortPath portPath;
\r
404 boolean isConnected;
\r
407 public static class JuceMidiOutputPort implements JuceMidiPort
\r
409 public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)
\r
412 androidPort = actualPort;
\r
413 portPath = portPathToUse;
\r
417 protected void finalize() throws Throwable
\r
424 public boolean isInputPort()
\r
430 public void start()
\r
440 public void sendMidi (byte[] msg, int offset, int count)
\r
442 if (androidPort != null)
\r
445 androidPort.send(msg, offset, count);
\r
446 } catch (IOException exception)
\r
448 Log.d ("JUCE", "send midi had IO exception");
\r
454 public void close()
\r
456 if (androidPort != null) {
\r
458 androidPort.close();
\r
459 } catch (IOException exception) {
\r
460 Log.d("JUCE", "IO Exception while closing port");
\r
465 owner.removePort (portPath);
\r
468 androidPort = null;
\r
471 MidiDeviceManager owner;
\r
472 MidiInputPort androidPort;
\r
473 MidiPortPath portPath;
\r
476 private static class MidiPortPath extends Object
\r
478 public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)
\r
480 deviceId = deviceIdToUse;
\r
481 isInput = direction;
\r
482 portIndex = androidIndex;
\r
486 public int deviceId;
\r
487 public int portIndex;
\r
488 public boolean isInput;
\r
491 public int hashCode()
\r
493 Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));
\r
494 return i.hashCode() * (isInput ? -1 : 1);
\r
498 public boolean equals (Object obj)
\r
503 if (getClass() != obj.getClass())
\r
506 MidiPortPath other = (MidiPortPath) obj;
\r
507 return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);
\r
511 //==============================================================================
\r
512 public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener
\r
514 //==============================================================================
\r
515 private class DummyBluetoothGattCallback extends BluetoothGattCallback
\r
517 public DummyBluetoothGattCallback (MidiDeviceManager mm)
\r
523 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
\r
525 if (newState == BluetoothProfile.STATE_CONNECTED)
\r
527 gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
\r
528 owner.pairBluetoothDeviceStepTwo (gatt.getDevice());
\r
531 public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
\r
532 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
\r
533 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
\r
534 public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}
\r
535 public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
\r
536 public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
\r
537 public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}
\r
538 public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}
\r
539 public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
\r
541 private MidiDeviceManager owner;
\r
544 //==============================================================================
\r
545 private class MidiDeviceOpenTask extends java.util.TimerTask
\r
547 public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)
\r
549 owner = deviceManager;
\r
550 midiDevice = device;
\r
551 btGatt = gattToUse;
\r
555 public boolean cancel()
\r
557 synchronized (MidiDeviceOpenTask.class)
\r
560 boolean retval = super.cancel();
\r
562 if (btGatt != null)
\r
564 btGatt.disconnect();
\r
570 if (midiDevice != null)
\r
574 midiDevice.close();
\r
576 catch (IOException e)
\r
586 public String getBluetoothAddress()
\r
588 synchronized (MidiDeviceOpenTask.class)
\r
590 if (midiDevice != null)
\r
592 MidiDeviceInfo info = midiDevice.getInfo();
\r
593 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
595 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
596 if (btDevice != null)
\r
597 return btDevice.getAddress();
\r
605 public BluetoothGatt getGatt() { return btGatt; }
\r
609 return midiDevice.getInfo().getId();
\r
615 synchronized (MidiDeviceOpenTask.class)
\r
617 if (owner != null && midiDevice != null)
\r
618 owner.onDeviceOpenedDelayed (midiDevice);
\r
622 private MidiDeviceManager owner;
\r
623 private MidiDevice midiDevice;
\r
624 private BluetoothGatt btGatt;
\r
627 //==============================================================================
\r
628 public MidiDeviceManager()
\r
630 manager = (MidiManager) getSystemService (MIDI_SERVICE);
\r
632 if (manager == null)
\r
634 Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
\r
638 openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();
\r
639 midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();
\r
640 openTasks = new HashMap<Integer, MidiDeviceOpenTask>();
\r
641 btDevicesPairing = new HashMap<String, BluetoothGatt>();
\r
643 MidiDeviceInfo[] foundDevices = manager.getDevices();
\r
644 for (MidiDeviceInfo info : foundDevices)
\r
645 onDeviceAdded (info);
\r
647 manager.registerDeviceCallback (this, null);
\r
650 protected void finalize() throws Throwable
\r
652 manager.unregisterDeviceCallback (this);
\r
654 synchronized (MidiDeviceManager.class)
\r
656 btDevicesPairing.clear();
\r
658 for (Integer deviceID : openTasks.keySet())
\r
659 openTasks.get (deviceID).cancel();
\r
664 for (MidiPortPath key : openPorts.keySet())
\r
665 openPorts.get (key).get().close();
\r
669 for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)
\r
671 if (device.second != null)
\r
673 device.second.disconnect();
\r
674 device.second.close();
\r
677 device.first.close();
\r
680 midiDevices.clear();
\r
685 public String[] getJuceAndroidMidiInputDevices()
\r
687 return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
\r
690 public String[] getJuceAndroidMidiOutputDevices()
\r
692 return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
\r
695 private String[] getJuceAndroidMidiDevices (int portType)
\r
697 // only update the list when JUCE asks for a new list
\r
698 synchronized (MidiDeviceManager.class)
\r
700 deviceInfos = getDeviceInfos();
\r
703 ArrayList<String> portNames = new ArrayList<String>();
\r
706 for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))
\r
707 portNames.add (getPortName (portInfo));
\r
709 String[] names = new String[portNames.size()];
\r
710 return portNames.toArray (names);
\r
713 private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)
\r
715 synchronized (MidiDeviceManager.class)
\r
717 int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);
\r
718 MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);
\r
720 if (portInfo != null)
\r
722 // ports must be opened exclusively!
\r
723 if (openPorts.containsKey (portInfo))
\r
726 Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);
\r
728 if (devicePair != null)
\r
730 MidiDevice device = devicePair.first;
\r
731 if (device != null)
\r
733 JuceMidiPort juceMidiPort = null;
\r
737 MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);
\r
739 if (outputPort != null)
\r
740 juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);
\r
744 MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);
\r
746 if (inputPort != null)
\r
747 juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);
\r
750 if (juceMidiPort != null)
\r
752 openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));
\r
754 return juceMidiPort;
\r
764 public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
\r
766 return openMidiPortWithJuceIndex (index, host, true);
\r
769 public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
\r
771 return openMidiPortWithJuceIndex (index, 0, false);
\r
774 /* 0: unpaired, 1: paired, 2: pairing */
\r
775 public int getBluetoothDeviceStatus (String address)
\r
777 synchronized (MidiDeviceManager.class)
\r
779 if (! address.isEmpty())
\r
781 if (findMidiDeviceForBluetoothAddress (address) != null)
\r
784 if (btDevicesPairing.containsKey (address))
\r
787 if (findOpenTaskForBluetoothAddress (address) != null)
\r
795 public boolean pairBluetoothDevice (BluetoothDevice btDevice)
\r
797 String btAddress = btDevice.getAddress();
\r
798 if (btAddress.isEmpty())
\r
801 synchronized (MidiDeviceManager.class)
\r
803 if (getBluetoothDeviceStatus (btAddress) != 0)
\r
807 btDevicesPairing.put (btDevice.getAddress(), null);
\r
808 BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));
\r
812 btDevicesPairing.put (btDevice.getAddress(), gatt);
\r
816 pairBluetoothDeviceStepTwo (btDevice);
\r
823 public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)
\r
825 manager.openBluetoothDevice(btDevice, this, null);
\r
828 public void unpairBluetoothDevice (String address)
\r
830 if (address.isEmpty())
\r
833 synchronized (MidiDeviceManager.class)
\r
835 if (btDevicesPairing.containsKey (address))
\r
837 BluetoothGatt gatt = btDevicesPairing.get (address);
\r
844 btDevicesPairing.remove (address);
\r
847 MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);
\r
848 if (openTask != null)
\r
850 int deviceID = openTask.getID();
\r
852 openTasks.remove (deviceID);
\r
855 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);
\r
856 if (midiDevicePair != null)
\r
858 MidiDevice midiDevice = midiDevicePair.first;
\r
859 onDeviceRemoved (midiDevice.getInfo());
\r
862 midiDevice.close();
\r
864 catch (IOException exception)
\r
866 Log.d ("JUCE", "IOException while closing midi device");
\r
872 private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)
\r
874 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
876 MidiDeviceInfo info = midiDevice.first.getInfo();
\r
877 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
879 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
880 if (btDevice != null && btDevice.getAddress().equals (address))
\r
888 private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)
\r
890 for (Integer deviceID : openTasks.keySet())
\r
892 MidiDeviceOpenTask openTask = openTasks.get (deviceID);
\r
893 if (openTask.getBluetoothAddress().equals (address))
\r
900 public void removePort (MidiPortPath path)
\r
902 openPorts.remove (path);
\r
905 public String getInputPortNameForJuceIndex (int index)
\r
907 MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);
\r
908 if (portInfo != null)
\r
909 return getPortName (portInfo);
\r
914 public String getOutputPortNameForJuceIndex (int index)
\r
916 MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);
\r
917 if (portInfo != null)
\r
918 return getPortName (portInfo);
\r
923 public void onDeviceAdded (MidiDeviceInfo info)
\r
925 // only add standard midi devices
\r
926 if (info.getType() == info.TYPE_BLUETOOTH)
\r
929 manager.openDevice (info, this, null);
\r
932 public void onDeviceRemoved (MidiDeviceInfo info)
\r
934 synchronized (MidiDeviceManager.class)
\r
936 Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());
\r
938 if (devicePair != null)
\r
940 MidiDevice midiDevice = devicePair.first;
\r
941 BluetoothGatt gatt = devicePair.second;
\r
943 // close all ports that use this device
\r
944 boolean removedPort = true;
\r
946 while (removedPort == true)
\r
948 removedPort = false;
\r
949 for (MidiPortPath key : openPorts.keySet())
\r
951 if (key.deviceId == info.getId())
\r
953 openPorts.get(key).get().close();
\r
954 removedPort = true;
\r
966 midiDevices.remove (devicePair);
\r
971 public void onDeviceStatusChanged (MidiDeviceStatus status)
\r
976 public void onDeviceOpened (MidiDevice theDevice)
\r
978 synchronized (MidiDeviceManager.class)
\r
980 MidiDeviceInfo info = theDevice.getInfo();
\r
981 int deviceID = info.getId();
\r
982 BluetoothGatt gatt = null;
\r
983 boolean isBluetooth = false;
\r
985 if (! openTasks.containsKey (deviceID))
\r
987 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
989 isBluetooth = true;
\r
990 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
991 if (btDevice != null)
\r
993 String btAddress = btDevice.getAddress();
\r
994 if (btDevicesPairing.containsKey (btAddress))
\r
996 gatt = btDevicesPairing.get (btAddress);
\r
997 btDevicesPairing.remove (btAddress);
\r
1001 // unpair was called in the mean time
\r
1004 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
\r
1005 if (midiDevicePair != null)
\r
1007 gatt = midiDevicePair.second;
\r
1011 gatt.disconnect();
\r
1016 theDevice.close();
\r
1018 catch (IOException e)
\r
1026 MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);
\r
1027 openTasks.put (deviceID, openTask);
\r
1029 new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));
\r
1034 public void onDeviceOpenedDelayed (MidiDevice theDevice)
\r
1036 synchronized (MidiDeviceManager.class)
\r
1038 int deviceID = theDevice.getInfo().getId();
\r
1040 if (openTasks.containsKey (deviceID))
\r
1042 if (! midiDevices.contains(theDevice))
\r
1044 BluetoothGatt gatt = openTasks.get (deviceID).getGatt();
\r
1045 openTasks.remove (deviceID);
\r
1046 midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));
\r
1051 // unpair was called in the mean time
\r
1052 MidiDeviceInfo info = theDevice.getInfo();
\r
1053 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
1054 if (btDevice != null)
\r
1056 String btAddress = btDevice.getAddress();
\r
1057 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
\r
1058 if (midiDevicePair != null)
\r
1060 BluetoothGatt gatt = midiDevicePair.second;
\r
1064 gatt.disconnect();
\r
1072 theDevice.close();
\r
1074 catch (IOException e)
\r
1080 public String getPortName(MidiPortPath path)
\r
1082 int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
\r
1084 synchronized (MidiDeviceManager.class)
\r
1086 for (MidiDeviceInfo info : deviceInfos)
\r
1088 int localIndex = 0;
\r
1089 if (info.getId() == path.deviceId)
\r
1091 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
\r
1093 int portType = portInfo.getType();
\r
1094 if (portType == portTypeToFind)
\r
1096 int portIndex = portInfo.getPortNumber();
\r
1097 if (portIndex == path.portIndex)
\r
1099 String portName = portInfo.getName();
\r
1100 if (portName.isEmpty())
\r
1101 portName = (String) info.getProperties().get(info.PROPERTY_NAME);
\r
1114 public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
\r
1117 for (MidiDeviceInfo info : deviceInfos)
\r
1119 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
\r
1121 if (portInfo.getType() == portType)
\r
1123 if (portIdx == juceIndex)
\r
1124 return new MidiPortPath (info.getId(),
\r
1125 (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
\r
1126 portInfo.getPortNumber());
\r
1136 private MidiDeviceInfo[] getDeviceInfos()
\r
1138 synchronized (MidiDeviceManager.class)
\r
1140 MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];
\r
1143 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
1144 infos[idx++] = midiDevice.first.getInfo();
\r
1150 private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)
\r
1152 synchronized (MidiDeviceManager.class)
\r
1154 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
1155 if (midiDevice.first.getInfo().getId() == deviceId)
\r
1156 return midiDevice;
\r
1162 private MidiManager manager;
\r
1163 private HashMap<String, BluetoothGatt> btDevicesPairing;
\r
1164 private HashMap<Integer, MidiDeviceOpenTask> openTasks;
\r
1165 private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;
\r
1166 private MidiDeviceInfo[] deviceInfos;
\r
1167 private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;
\r
1170 public MidiDeviceManager getAndroidMidiDeviceManager()
\r
1172 if (getSystemService (MIDI_SERVICE) == null)
\r
1175 synchronized (AudioPluginHost.class)
\r
1177 if (midiDeviceManager == null)
\r
1178 midiDeviceManager = new MidiDeviceManager();
\r
1181 return midiDeviceManager;
\r
1184 public BluetoothManager getAndroidBluetoothManager()
\r
1186 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
\r
1188 if (adapter == null)
\r
1191 if (adapter.getBluetoothLeScanner() == null)
\r
1194 synchronized (AudioPluginHost.class)
\r
1196 if (bluetoothManager == null)
\r
1197 bluetoothManager = new BluetoothManager();
\r
1200 return bluetoothManager;
\r
1203 //==============================================================================
\r
1205 public void onCreate (Bundle savedInstanceState)
\r
1207 super.onCreate (savedInstanceState);
\r
1209 isScreenSaverEnabled = true;
\r
1211 viewHolder = new ViewHolder (this);
\r
1212 setContentView (viewHolder);
\r
1214 setVolumeControlStream (AudioManager.STREAM_MUSIC);
\r
1216 permissionCallbackPtrMap = new HashMap<Integer, Long>();
\r
1217 appPausedResumedListeners = new HashMap<Long, AppPausedResumedListener>();
\r
1221 protected void onDestroy()
\r
1224 super.onDestroy();
\r
1230 protected void onPause()
\r
1234 Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
\r
1236 for (Long k : keys)
\r
1237 appPausedResumedListeners.get (k).appPaused();
\r
1241 Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
\r
1242 // openGL glitches when pausing/resuming apps..
\r
1243 } catch (InterruptedException e) {}
\r
1249 protected void onResume()
\r
1254 Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
\r
1256 for (Long k : keys)
\r
1257 appPausedResumedListeners.get (k).appResumed();
\r
1261 public void onConfigurationChanged (Configuration cfg)
\r
1263 super.onConfigurationChanged (cfg);
\r
1264 setContentView (viewHolder);
\r
1267 private void callAppLauncher()
\r
1269 launchApp (getApplicationInfo().publicSourceDir,
\r
1270 getApplicationInfo().dataDir);
\r
1273 // Need to override this as the default implementation always finishes the activity.
\r
1275 public void onBackPressed()
\r
1277 ComponentPeerView focusedView = getViewWithFocusOrDefaultView();
\r
1279 if (focusedView == null)
\r
1282 focusedView.backButtonPressed();
\r
1285 private ComponentPeerView getViewWithFocusOrDefaultView()
\r
1287 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
1289 if (viewHolder.getChildAt (i).hasFocus())
\r
1290 return (ComponentPeerView) viewHolder.getChildAt (i);
\r
1293 if (viewHolder.getChildCount() > 0)
\r
1294 return (ComponentPeerView) viewHolder.getChildAt (0);
\r
1299 //==============================================================================
\r
1300 private void hideActionBar()
\r
1302 // get "getActionBar" method
\r
1303 java.lang.reflect.Method getActionBarMethod = null;
\r
1306 getActionBarMethod = this.getClass().getMethod ("getActionBar");
\r
1308 catch (SecurityException e) { return; }
\r
1309 catch (NoSuchMethodException e) { return; }
\r
1310 if (getActionBarMethod == null) return;
\r
1312 // invoke "getActionBar" method
\r
1313 Object actionBar = null;
\r
1316 actionBar = getActionBarMethod.invoke (this);
\r
1318 catch (java.lang.IllegalArgumentException e) { return; }
\r
1319 catch (java.lang.IllegalAccessException e) { return; }
\r
1320 catch (java.lang.reflect.InvocationTargetException e) { return; }
\r
1321 if (actionBar == null) return;
\r
1323 // get "hide" method
\r
1324 java.lang.reflect.Method actionBarHideMethod = null;
\r
1327 actionBarHideMethod = actionBar.getClass().getMethod ("hide");
\r
1329 catch (SecurityException e) { return; }
\r
1330 catch (NoSuchMethodException e) { return; }
\r
1331 if (actionBarHideMethod == null) return;
\r
1333 // invoke "hide" method
\r
1336 actionBarHideMethod.invoke (actionBar);
\r
1338 catch (java.lang.IllegalArgumentException e) {}
\r
1339 catch (java.lang.IllegalAccessException e) {}
\r
1340 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1343 void requestPermissionsCompat (String[] permissions, int requestCode)
\r
1345 Method requestPermissionsMethod = null;
\r
1348 requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",
\r
1349 String[].class, int.class);
\r
1351 catch (SecurityException e) { return; }
\r
1352 catch (NoSuchMethodException e) { return; }
\r
1353 if (requestPermissionsMethod == null) return;
\r
1357 requestPermissionsMethod.invoke (this, permissions, requestCode);
\r
1359 catch (java.lang.IllegalArgumentException e) {}
\r
1360 catch (java.lang.IllegalAccessException e) {}
\r
1361 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1364 //==============================================================================
\r
1365 private native void launchApp (String appFile, String appDataDir);
\r
1366 private native void quitApp();
\r
1367 private native void suspendApp();
\r
1368 private native void resumeApp();
\r
1369 private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
\r
1370 private native void appActivityResult (int requestCode, int resultCode, Intent data);
\r
1371 private native void appNewIntent (Intent intent);
\r
1373 //==============================================================================
\r
1374 private ViewHolder viewHolder;
\r
1375 private MidiDeviceManager midiDeviceManager = null;
\r
1376 private BluetoothManager bluetoothManager = null;
\r
1377 private boolean isScreenSaverEnabled;
\r
1378 private java.util.Timer keepAliveTimer;
\r
1380 public final ComponentPeerView createNewView (boolean opaque, long host)
\r
1382 ComponentPeerView v = new ComponentPeerView (this, opaque, host);
\r
1383 viewHolder.addView (v);
\r
1384 addAppPausedResumedListener (v, host);
\r
1388 public final void deleteView (ComponentPeerView view)
\r
1390 removeAppPausedResumedListener (view, view.host);
\r
1394 ViewGroup group = (ViewGroup) (view.getParent());
\r
1396 if (group != null)
\r
1397 group.removeView (view);
\r
1400 public final void deleteNativeSurfaceView (NativeSurfaceView view)
\r
1402 ViewGroup group = (ViewGroup) (view.getParent());
\r
1404 if (group != null)
\r
1405 group.removeView (view);
\r
1408 final class ViewHolder extends ViewGroup
\r
1410 public ViewHolder (Context context)
\r
1413 setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
\r
1414 setFocusable (false);
\r
1417 protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1419 setScreenSize (getWidth(), getHeight(), getDPI());
\r
1421 if (isFirstResize)
\r
1423 isFirstResize = false;
\r
1424 callAppLauncher();
\r
1428 private final int getDPI()
\r
1430 DisplayMetrics metrics = new DisplayMetrics();
\r
1431 getWindowManager().getDefaultDisplay().getMetrics (metrics);
\r
1432 return metrics.densityDpi;
\r
1435 private boolean isFirstResize = true;
\r
1438 public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
\r
1440 canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
\r
1443 //==============================================================================
\r
1444 public final void setScreenSaver (boolean enabled)
\r
1446 if (isScreenSaverEnabled != enabled)
\r
1448 isScreenSaverEnabled = enabled;
\r
1450 if (keepAliveTimer != null)
\r
1452 keepAliveTimer.cancel();
\r
1453 keepAliveTimer = null;
\r
1458 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1462 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1464 // If no user input is received after about 3 seconds, the OS will lower the
\r
1465 // task's priority, so this timer forces it to be kept active.
\r
1466 keepAliveTimer = new java.util.Timer();
\r
1468 keepAliveTimer.scheduleAtFixedRate (new TimerTask()
\r
1473 android.app.Instrumentation instrumentation = new android.app.Instrumentation();
\r
1477 instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
\r
1479 catch (Exception e)
\r
1488 public final boolean getScreenSaver()
\r
1490 return isScreenSaverEnabled;
\r
1493 //==============================================================================
\r
1494 public final String getClipboardContent()
\r
1496 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1498 CharSequence content = clipboard.getText();
\r
1499 return content != null ? content.toString() : new String();
\r
1502 public final void setClipboardContent (String newText)
\r
1504 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1505 clipboard.setText (newText);
\r
1508 //==============================================================================
\r
1509 public final void showMessageBox (String title, String message, final long callback)
\r
1511 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1512 builder.setTitle (title)
\r
1513 .setMessage (message)
\r
1514 .setCancelable (true)
\r
1515 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1517 public void onCancel (DialogInterface dialog)
\r
1519 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1522 .setPositiveButton ("OK", new DialogInterface.OnClickListener()
\r
1524 public void onClick (DialogInterface dialog, int id)
\r
1527 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1531 builder.create().show();
\r
1534 public final void showOkCancelBox (String title, String message, final long callback,
\r
1535 String okButtonText, String cancelButtonText)
\r
1537 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1538 builder.setTitle (title)
\r
1539 .setMessage (message)
\r
1540 .setCancelable (true)
\r
1541 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1543 public void onCancel (DialogInterface dialog)
\r
1545 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1548 .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()
\r
1550 public void onClick (DialogInterface dialog, int id)
\r
1553 AudioPluginHost.this.alertDismissed (callback, 1);
\r
1556 .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()
\r
1558 public void onClick (DialogInterface dialog, int id)
\r
1561 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1565 builder.create().show();
\r
1568 public final void showYesNoCancelBox (String title, String message, final long callback)
\r
1570 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1571 builder.setTitle (title)
\r
1572 .setMessage (message)
\r
1573 .setCancelable (true)
\r
1574 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1576 public void onCancel (DialogInterface dialog)
\r
1578 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1581 .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
\r
1583 public void onClick (DialogInterface dialog, int id)
\r
1586 AudioPluginHost.this.alertDismissed (callback, 1);
\r
1589 .setNegativeButton ("No", new DialogInterface.OnClickListener()
\r
1591 public void onClick (DialogInterface dialog, int id)
\r
1594 AudioPluginHost.this.alertDismissed (callback, 2);
\r
1597 .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
\r
1599 public void onClick (DialogInterface dialog, int id)
\r
1602 AudioPluginHost.this.alertDismissed (callback, 0);
\r
1606 builder.create().show();
\r
1609 public native void alertDismissed (long callback, int id);
\r
1611 //==============================================================================
\r
1612 public interface AppPausedResumedListener
\r
1615 void appResumed();
\r
1618 private Map<Long, AppPausedResumedListener> appPausedResumedListeners;
\r
1620 public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
\r
1622 appPausedResumedListeners.put (new Long (listenerHost), l);
\r
1625 public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
\r
1627 appPausedResumedListeners.remove (new Long (listenerHost));
\r
1630 //==============================================================================
\r
1631 public final class ComponentPeerView extends ViewGroup
\r
1632 implements View.OnFocusChangeListener, AppPausedResumedListener
\r
1634 public ComponentPeerView (Context context, boolean opaque_, long host)
\r
1638 setWillNotDraw (false);
\r
1641 setFocusable (true);
\r
1642 setFocusableInTouchMode (true);
\r
1643 setOnFocusChangeListener (this);
\r
1645 // swap red and blue colours to match internal opengl texture format
\r
1646 ColorMatrix colorMatrix = new ColorMatrix();
\r
1648 float[] colorTransform = { 0, 0, 1.0f, 0, 0,
\r
1651 0, 0, 0, 1.0f, 0 };
\r
1653 colorMatrix.set (colorTransform);
\r
1654 paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
\r
1656 java.lang.reflect.Method method = null;
\r
1660 method = getClass().getMethod ("setLayerType", int.class, Paint.class);
\r
1662 catch (SecurityException e) {}
\r
1663 catch (NoSuchMethodException e) {}
\r
1665 if (method != null)
\r
1669 int layerTypeNone = 0;
\r
1670 method.invoke (this, layerTypeNone, null);
\r
1672 catch (java.lang.IllegalArgumentException e) {}
\r
1673 catch (java.lang.IllegalAccessException e) {}
\r
1674 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1678 //==============================================================================
\r
1679 private native void handlePaint (long host, Canvas canvas, Paint paint);
\r
1682 public void onDraw (Canvas canvas)
\r
1687 handlePaint (host, canvas, paint);
\r
1691 public boolean isOpaque()
\r
1696 private boolean opaque;
\r
1697 private long host;
\r
1698 private Paint paint = new Paint();
\r
1700 //==============================================================================
\r
1701 private native void handleMouseDown (long host, int index, float x, float y, long time);
\r
1702 private native void handleMouseDrag (long host, int index, float x, float y, long time);
\r
1703 private native void handleMouseUp (long host, int index, float x, float y, long time);
\r
1706 public boolean onTouchEvent (MotionEvent event)
\r
1711 int action = event.getAction();
\r
1712 long time = event.getEventTime();
\r
1714 switch (action & MotionEvent.ACTION_MASK)
\r
1716 case MotionEvent.ACTION_DOWN:
\r
1717 handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1720 case MotionEvent.ACTION_CANCEL:
\r
1721 case MotionEvent.ACTION_UP:
\r
1722 handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1725 case MotionEvent.ACTION_MOVE:
\r
1727 int n = event.getPointerCount();
\r
1728 for (int i = 0; i < n; ++i)
\r
1729 handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1734 case MotionEvent.ACTION_POINTER_UP:
\r
1736 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1737 handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1741 case MotionEvent.ACTION_POINTER_DOWN:
\r
1743 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1744 handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1755 //==============================================================================
\r
1756 private native void handleKeyDown (long host, int keycode, int textchar);
\r
1757 private native void handleKeyUp (long host, int keycode, int textchar);
\r
1758 private native void handleBackButton (long host);
\r
1759 private native void handleKeyboardHidden (long host);
\r
1761 public void showKeyboard (String type)
\r
1763 InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
\r
1767 if (type.length() > 0)
\r
1769 imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
\r
1770 imm.setInputMethod (getWindowToken(), type);
\r
1771 keyboardDismissListener.startListening();
\r
1775 imm.hideSoftInputFromWindow (getWindowToken(), 0);
\r
1776 keyboardDismissListener.stopListening();
\r
1781 public void backButtonPressed()
\r
1786 handleBackButton (host);
\r
1790 public boolean onKeyDown (int keyCode, KeyEvent event)
\r
1797 case KeyEvent.KEYCODE_VOLUME_UP:
\r
1798 case KeyEvent.KEYCODE_VOLUME_DOWN:
\r
1799 return super.onKeyDown (keyCode, event);
\r
1800 case KeyEvent.KEYCODE_BACK:
\r
1802 ((Activity) getContext()).onBackPressed();
\r
1810 handleKeyDown (host, keyCode, event.getUnicodeChar());
\r
1815 public boolean onKeyUp (int keyCode, KeyEvent event)
\r
1820 handleKeyUp (host, keyCode, event.getUnicodeChar());
\r
1825 public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
\r
1830 if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
\r
1831 return super.onKeyMultiple (keyCode, count, event);
\r
1833 if (event.getCharacters() != null)
\r
1835 int utf8Char = event.getCharacters().codePointAt (0);
\r
1836 handleKeyDown (host, utf8Char, utf8Char);
\r
1843 //==============================================================================
\r
1844 private final class KeyboardDismissListener
\r
1846 public KeyboardDismissListener (ComponentPeerView viewToUse)
\r
1851 private void startListening()
\r
1853 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);
\r
1856 private void stopListening()
\r
1858 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);
\r
1861 private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
\r
1865 keyboardShown = false;
\r
1869 public void onGlobalLayout()
\r
1871 Rect r = new Rect();
\r
1873 ViewGroup parentView = (ViewGroup) getParent();
\r
1875 if (parentView == null)
\r
1878 parentView.getWindowVisibleDisplayFrame (r);
\r
1880 int diff = parentView.getHeight() - (r.bottom - r.top);
\r
1882 // Arbitrary threshold, surely keyboard would take more than 20 pix.
\r
1883 if (diff < 20 && keyboardShown)
\r
1885 keyboardShown = false;
\r
1886 handleKeyboardHidden (view.host);
\r
1889 if (! keyboardShown && diff > 20)
\r
1890 keyboardShown = true;
\r
1893 private boolean keyboardShown;
\r
1896 private ComponentPeerView view;
\r
1897 private TreeObserver viewTreeObserver = new TreeObserver();
\r
1900 private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);
\r
1902 // this is here to make keyboard entry work on a Galaxy Tab2 10.1
\r
1904 public InputConnection onCreateInputConnection (EditorInfo outAttrs)
\r
1906 outAttrs.actionLabel = "";
\r
1907 outAttrs.hintText = "";
\r
1908 outAttrs.initialCapsMode = 0;
\r
1909 outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
\r
1910 outAttrs.label = "";
\r
1911 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
\r
1912 outAttrs.inputType = InputType.TYPE_NULL;
\r
1914 return new BaseInputConnection (this, false);
\r
1917 //==============================================================================
\r
1919 protected void onSizeChanged (int w, int h, int oldw, int oldh)
\r
1924 super.onSizeChanged (w, h, oldw, oldh);
\r
1925 viewSizeChanged (host);
\r
1929 protected void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1931 for (int i = getChildCount(); --i >= 0;)
\r
1932 requestTransparentRegion (getChildAt (i));
\r
1935 private native void viewSizeChanged (long host);
\r
1938 public void onFocusChange (View v, boolean hasFocus)
\r
1944 focusChanged (host, hasFocus);
\r
1947 private native void focusChanged (long host, boolean hasFocus);
\r
1949 public void setViewName (String newName) {}
\r
1951 public void setSystemUiVisibilityCompat (int visibility)
\r
1953 Method systemUIVisibilityMethod = null;
\r
1956 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
\r
1958 catch (SecurityException e) { return; }
\r
1959 catch (NoSuchMethodException e) { return; }
\r
1960 if (systemUIVisibilityMethod == null) return;
\r
1964 systemUIVisibilityMethod.invoke (this, visibility);
\r
1966 catch (java.lang.IllegalArgumentException e) {}
\r
1967 catch (java.lang.IllegalAccessException e) {}
\r
1968 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1971 public boolean isVisible() { return getVisibility() == VISIBLE; }
\r
1972 public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
\r
1974 public boolean containsPoint (int x, int y)
\r
1976 return true; //xxx needs to check overlapping views
\r
1979 //==============================================================================
\r
1980 private native void handleAppPaused (long host);
\r
1981 private native void handleAppResumed (long host);
\r
1984 public void appPaused()
\r
1989 handleAppPaused (host);
\r
1993 public void appResumed()
\r
1998 // Ensure that navigation/status bar visibility is correctly restored.
\r
1999 handleAppResumed (host);
\r
2003 //==============================================================================
\r
2004 public static class NativeSurfaceView extends SurfaceView
\r
2005 implements SurfaceHolder.Callback
\r
2007 private long nativeContext = 0;
\r
2008 private boolean forVideo;
\r
2010 NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
\r
2013 nativeContext = nativeContextPtr;
\r
2014 forVideo = createdForVideo;
\r
2017 public Surface getNativeSurface()
\r
2019 Surface retval = null;
\r
2021 SurfaceHolder holder = getHolder();
\r
2022 if (holder != null)
\r
2023 retval = holder.getSurface();
\r
2028 //==============================================================================
\r
2030 public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
\r
2033 surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
\r
2035 surfaceChangedNative (nativeContext, holder, format, width, height);
\r
2039 public void surfaceCreated (SurfaceHolder holder)
\r
2042 surfaceCreatedNativeVideo (nativeContext, holder);
\r
2044 surfaceCreatedNative (nativeContext, holder);
\r
2048 public void surfaceDestroyed (SurfaceHolder holder)
\r
2051 surfaceDestroyedNativeVideo (nativeContext, holder);
\r
2053 surfaceDestroyedNative (nativeContext, holder);
\r
2057 protected void dispatchDraw (Canvas canvas)
\r
2059 super.dispatchDraw (canvas);
\r
2062 dispatchDrawNativeVideo (nativeContext, canvas);
\r
2064 dispatchDrawNative (nativeContext, canvas);
\r
2067 //==============================================================================
\r
2069 protected void onAttachedToWindow()
\r
2071 super.onAttachedToWindow();
\r
2072 getHolder().addCallback (this);
\r
2076 protected void onDetachedFromWindow()
\r
2078 super.onDetachedFromWindow();
\r
2079 getHolder().removeCallback (this);
\r
2082 //==============================================================================
\r
2083 private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
\r
2084 private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
\r
2085 private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
\r
2086 private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
\r
2087 int format, int width, int height);
\r
2089 private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
\r
2090 private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
\r
2091 private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
\r
2092 private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
\r
2093 int format, int width, int height);
\r
2096 public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
\r
2098 return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
\r
2101 //==============================================================================
\r
2102 public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)
\r
2104 Path p = new Path();
\r
2106 char[] str = { glyph1, glyph2 };
\r
2107 paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);
\r
2109 RectF boundsF = new RectF();
\r
2110 p.computeBounds (boundsF, true);
\r
2111 matrix.mapRect (boundsF);
\r
2113 boundsF.roundOut (bounds);
\r
2117 final int w = bounds.width();
\r
2118 final int h = Math.max (1, bounds.height());
\r
2120 Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
\r
2122 Canvas c = new Canvas (bm);
\r
2123 matrix.postTranslate (-bounds.left, -bounds.top);
\r
2124 c.setMatrix (matrix);
\r
2125 c.drawPath (p, paint);
\r
2127 final int sizeNeeded = w * h;
\r
2128 if (cachedRenderArray.length < sizeNeeded)
\r
2129 cachedRenderArray = new int [sizeNeeded];
\r
2131 bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
\r
2133 return cachedRenderArray;
\r
2136 private int[] cachedRenderArray = new int [256];
\r
2138 //==============================================================================
\r
2139 public static class NativeInvocationHandler implements InvocationHandler
\r
2141 public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)
\r
2143 activity = activityToUse;
\r
2144 nativeContext = nativeContextRef;
\r
2147 public void nativeContextDeleted()
\r
2149 nativeContext = 0;
\r
2153 public void finalize()
\r
2155 activity.runOnUiThread (new Runnable()
\r
2160 if (nativeContext != 0)
\r
2161 dispatchFinalize (nativeContext);
\r
2167 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
\r
2169 return dispatchInvoke (nativeContext, proxy, method, args);
\r
2172 //==============================================================================
\r
2173 Activity activity;
\r
2174 private long nativeContext = 0;
\r
2176 private native void dispatchFinalize (long nativeContextRef);
\r
2177 private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);
\r
2180 public InvocationHandler createInvocationHandler (long nativeContextRef)
\r
2182 return new NativeInvocationHandler (this, nativeContextRef);
\r
2185 public void invocationHandlerContextDeleted (InvocationHandler handler)
\r
2187 ((NativeInvocationHandler) handler).nativeContextDeleted();
\r
2190 //==============================================================================
\r
2191 public static class HTTPStream
\r
2193 public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,
\r
2194 String headersToUse, int timeOutMsToUse,
\r
2195 int[] statusCodeToUse, StringBuffer responseHeadersToUse,
\r
2196 int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
\r
2198 isPost = isPostToUse;
\r
2199 postData = postDataToUse;
\r
2200 headers = headersToUse;
\r
2201 timeOutMs = timeOutMsToUse;
\r
2202 statusCode = statusCodeToUse;
\r
2203 responseHeaders = responseHeadersToUse;
\r
2205 numRedirectsToFollow = numRedirectsToFollowToUse;
\r
2206 httpRequestCmd = httpRequestCmdToUse;
\r
2208 connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);
\r
2211 private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,
\r
2212 String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
\r
2214 HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
\r
2218 newConnection.setInstanceFollowRedirects (false);
\r
2219 newConnection.setConnectTimeout (timeOutMs);
\r
2220 newConnection.setReadTimeout (timeOutMs);
\r
2222 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.
\r
2223 // So convert headers string to an array, with an element for each line
\r
2224 String headerLines[] = headers.split("\\n");
\r
2226 // Set request headers
\r
2227 for (int i = 0; i < headerLines.length; ++i)
\r
2229 int pos = headerLines[i].indexOf (":");
\r
2231 if (pos > 0 && pos < headerLines[i].length())
\r
2233 String field = headerLines[i].substring (0, pos);
\r
2234 String value = headerLines[i].substring (pos + 1);
\r
2236 if (value.length() > 0)
\r
2237 newConnection.setRequestProperty (field, value);
\r
2241 newConnection.setRequestMethod (httpRequestCmd);
\r
2245 newConnection.setDoOutput (true);
\r
2247 if (postData != null)
\r
2249 OutputStream out = newConnection.getOutputStream();
\r
2250 out.write(postData);
\r
2255 return newConnection;
\r
2257 catch (Throwable e)
\r
2259 newConnection.disconnect();
\r
2260 throw new IOException ("Connection error");
\r
2264 private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException
\r
2266 synchronized (createFutureLock)
\r
2268 if (hasBeenCancelled.get())
\r
2271 streamFuture = executor.submit (new Callable<BufferedInputStream>()
\r
2274 public BufferedInputStream call() throws IOException
\r
2276 return new BufferedInputStream (isInput ? connection.getInputStream()
\r
2277 : connection.getErrorStream());
\r
2284 return streamFuture.get();
\r
2286 catch (InterruptedException e)
\r
2290 catch (CancellationException e)
\r
2296 public final boolean connect()
\r
2298 boolean result = false;
\r
2299 int numFollowedRedirects = 0;
\r
2303 result = doConnect();
\r
2308 if (++numFollowedRedirects > numRedirectsToFollow)
\r
2311 int status = statusCode[0];
\r
2313 if (status == 301 || status == 302 || status == 303 || status == 307)
\r
2315 // Assumes only one occurrence of "Location"
\r
2316 int pos1 = responseHeaders.indexOf ("Location:") + 10;
\r
2317 int pos2 = responseHeaders.indexOf ("\n", pos1);
\r
2321 String currentLocation = connection.getURL().toString();
\r
2322 String newLocation = responseHeaders.substring (pos1, pos2);
\r
2326 // Handle newLocation whether it's absolute or relative
\r
2327 URL baseUrl = new URL (currentLocation);
\r
2328 URL newUrl = new URL (baseUrl, newLocation);
\r
2329 String transformedNewLocation = newUrl.toString();
\r
2331 if (transformedNewLocation != currentLocation)
\r
2333 // Clear responseHeaders before next iteration
\r
2334 responseHeaders.delete (0, responseHeaders.length());
\r
2336 synchronized (createStreamLock)
\r
2338 if (hasBeenCancelled.get())
\r
2341 connection.disconnect();
\r
2345 connection = createConnection (transformedNewLocation, isPost,
\r
2346 postData, headers, timeOutMs,
\r
2349 catch (Throwable e)
\r
2360 catch (Throwable e)
\r
2379 private final boolean doConnect()
\r
2381 synchronized (createStreamLock)
\r
2383 if (hasBeenCancelled.get())
\r
2390 inputStream = getCancellableStream (true);
\r
2392 catch (ExecutionException e)
\r
2394 if (connection.getResponseCode() < 400)
\r
2396 statusCode[0] = connection.getResponseCode();
\r
2397 connection.disconnect();
\r
2403 statusCode[0] = connection.getResponseCode();
\r
2408 if (statusCode[0] >= 400)
\r
2409 inputStream = getCancellableStream (false);
\r
2411 inputStream = getCancellableStream (true);
\r
2413 catch (ExecutionException e)
\r
2416 for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
\r
2418 if (entry.getKey() != null && entry.getValue() != null)
\r
2420 responseHeaders.append(entry.getKey() + ": "
\r
2421 + android.text.TextUtils.join(",", entry.getValue()) + "\n");
\r
2423 if (entry.getKey().compareTo ("Content-Length") == 0)
\r
2424 totalLength = Integer.decode (entry.getValue().get (0));
\r
2430 catch (IOException e)
\r
2437 static class DisconnectionRunnable implements Runnable
\r
2439 public DisconnectionRunnable (HttpURLConnection theConnection,
\r
2440 InputStream theInputStream,
\r
2441 ReentrantLock theCreateStreamLock,
\r
2442 Object theCreateFutureLock,
\r
2443 Future<BufferedInputStream> theStreamFuture)
\r
2445 connectionToDisconnect = theConnection;
\r
2446 inputStream = theInputStream;
\r
2447 createStreamLock = theCreateStreamLock;
\r
2448 createFutureLock = theCreateFutureLock;
\r
2449 streamFuture = theStreamFuture;
\r
2456 if (! createStreamLock.tryLock())
\r
2458 synchronized (createFutureLock)
\r
2460 if (streamFuture != null)
\r
2461 streamFuture.cancel (true);
\r
2464 createStreamLock.lock();
\r
2467 if (connectionToDisconnect != null)
\r
2468 connectionToDisconnect.disconnect();
\r
2470 if (inputStream != null)
\r
2471 inputStream.close();
\r
2473 catch (IOException e)
\r
2477 createStreamLock.unlock();
\r
2481 private HttpURLConnection connectionToDisconnect;
\r
2482 private InputStream inputStream;
\r
2483 private ReentrantLock createStreamLock;
\r
2484 private Object createFutureLock;
\r
2485 Future<BufferedInputStream> streamFuture;
\r
2488 public final void release()
\r
2490 DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,
\r
2496 synchronized (createStreamLock)
\r
2498 hasBeenCancelled.set (true);
\r
2500 connection = null;
\r
2503 Thread disconnectionThread = new Thread(disconnectionRunnable);
\r
2504 disconnectionThread.start();
\r
2507 public final int read (byte[] buffer, int numBytes)
\r
2513 synchronized (createStreamLock)
\r
2515 if (inputStream != null)
\r
2516 num = inputStream.read (buffer, 0, numBytes);
\r
2519 catch (IOException e)
\r
2528 public final long getPosition() { return position; }
\r
2529 public final long getTotalLength() { return totalLength; }
\r
2530 public final boolean isExhausted() { return false; }
\r
2531 public final boolean setPosition (long newPos) { return false; }
\r
2533 private boolean isPost;
\r
2534 private byte[] postData;
\r
2535 private String headers;
\r
2536 private int timeOutMs;
\r
2537 String httpRequestCmd;
\r
2538 private HttpURLConnection connection;
\r
2539 private int[] statusCode;
\r
2540 private StringBuffer responseHeaders;
\r
2541 private int totalLength;
\r
2542 private int numRedirectsToFollow;
\r
2543 private InputStream inputStream;
\r
2544 private long position;
\r
2545 private final ReentrantLock createStreamLock = new ReentrantLock();
\r
2546 private final Object createFutureLock = new Object();
\r
2547 private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
\r
2549 private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());
\r
2550 Future<BufferedInputStream> streamFuture;
\r
2553 public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
\r
2554 String headers, int timeOutMs, int[] statusCode,
\r
2555 StringBuffer responseHeaders, int numRedirectsToFollow,
\r
2556 String httpRequestCmd)
\r
2558 // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
\r
2559 if (timeOutMs < 0)
\r
2561 else if (timeOutMs == 0)
\r
2562 timeOutMs = 30000;
\r
2568 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,
\r
2569 timeOutMs, statusCode, responseHeaders,
\r
2570 numRedirectsToFollow, httpRequestCmd);
\r
2572 return httpStream;
\r
2574 catch (Throwable e) {}
\r
2580 public final void launchURL (String url)
\r
2582 startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
\r
2585 private native boolean webViewPageLoadStarted (long host, WebView view, String url);
\r
2586 private native void webViewPageLoadFinished (long host, WebView view, String url);
\r
2587 private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error); private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse); private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
\r
2588 private native void webViewCloseWindowRequest (long host, WebView view);
\r
2589 private native void webViewCreateWindowRequest (long host, WebView view);
\r
2591 //==============================================================================
\r
2592 public class JuceWebViewClient extends WebViewClient
\r
2594 public JuceWebViewClient (long hostToUse)
\r
2599 public void hostDeleted()
\r
2601 synchronized (hostLock)
\r
2608 public void onPageFinished (WebView view, String url)
\r
2613 webViewPageLoadFinished (host, view, url);
\r
2617 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
\r
2622 webViewReceivedSslError (host, view, handler, error);
\r
2626 public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)
\r
2631 webViewReceivedError (host, view, request, error);
\r
2635 public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
\r
2640 webViewReceivedHttpError (host, view, request, errorResponse);
\r
2644 public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
\r
2646 synchronized (hostLock)
\r
2650 boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());
\r
2657 return new WebResourceResponse ("text/html", null, null);
\r
2660 private long host;
\r
2661 private final Object hostLock = new Object();
\r
2664 public class JuceWebChromeClient extends WebChromeClient
\r
2666 public JuceWebChromeClient (long hostToUse)
\r
2672 public void onCloseWindow (WebView window)
\r
2674 webViewCloseWindowRequest (host, window);
\r
2678 public boolean onCreateWindow (WebView view, boolean isDialog,
\r
2679 boolean isUserGesture, Message resultMsg)
\r
2681 webViewCreateWindowRequest (host, view);
\r
2685 private long host;
\r
2686 private final Object hostLock = new Object();
\r
2690 //==============================================================================
\r
2691 public class CameraDeviceStateCallback extends CameraDevice.StateCallback
\r
2693 private native void cameraDeviceStateClosed (long host, CameraDevice camera);
\r
2694 private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
\r
2695 private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
\r
2696 private native void cameraDeviceStateOpened (long host, CameraDevice camera);
\r
2698 CameraDeviceStateCallback (long hostToUse)
\r
2704 public void onClosed (CameraDevice camera)
\r
2706 cameraDeviceStateClosed (host, camera);
\r
2710 public void onDisconnected (CameraDevice camera)
\r
2712 cameraDeviceStateDisconnected (host, camera);
\r
2716 public void onError (CameraDevice camera, int error)
\r
2718 cameraDeviceStateError (host, camera, error);
\r
2722 public void onOpened (CameraDevice camera)
\r
2724 cameraDeviceStateOpened (host, camera);
\r
2727 private long host;
\r
2730 //==============================================================================
\r
2731 public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
\r
2733 private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
\r
2734 private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
\r
2735 private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
\r
2736 private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
\r
2737 private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
\r
2739 CameraCaptureSessionStateCallback (long hostToUse)
\r
2745 public void onActive (CameraCaptureSession session)
\r
2747 cameraCaptureSessionActive (host, session);
\r
2751 public void onClosed (CameraCaptureSession session)
\r
2753 cameraCaptureSessionClosed (host, session);
\r
2757 public void onConfigureFailed (CameraCaptureSession session)
\r
2759 cameraCaptureSessionConfigureFailed (host, session);
\r
2763 public void onConfigured (CameraCaptureSession session)
\r
2765 cameraCaptureSessionConfigured (host, session);
\r
2769 public void onReady (CameraCaptureSession session)
\r
2771 cameraCaptureSessionReady (host, session);
\r
2774 private long host;
\r
2777 //==============================================================================
\r
2778 public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
\r
2780 private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
\r
2781 private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
\r
2782 private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
\r
2783 private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
\r
2784 private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
\r
2785 private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
\r
2787 CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
\r
2790 preview = shouldBePreview;
\r
2794 public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
\r
2795 TotalCaptureResult result)
\r
2797 cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
\r
2801 public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
\r
2803 cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
\r
2807 public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
\r
2808 CaptureResult partialResult)
\r
2810 cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
\r
2814 public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
\r
2816 cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
\r
2820 public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
\r
2822 cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
\r
2826 public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
\r
2829 cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
\r
2832 private long host;
\r
2833 private boolean preview;
\r
2836 //==============================================================================
\r
2837 public class JuceOrientationEventListener extends OrientationEventListener
\r
2839 private native void deviceOrientationChanged (long host, int orientation);
\r
2841 public JuceOrientationEventListener (long hostToUse, Context context, int rate)
\r
2843 super (context, rate);
\r
2849 public void onOrientationChanged (int orientation)
\r
2851 deviceOrientationChanged (host, orientation);
\r
2854 private long host;
\r
2858 //==============================================================================
\r
2859 public class MediaControllerCallback extends MediaController.Callback
\r
2861 private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
\r
2862 private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
\r
2863 private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
\r
2864 private native void mediaControllerSessionDestroyed (long host);
\r
2866 MediaControllerCallback (long hostToUse)
\r
2872 public void onAudioInfoChanged (MediaController.PlaybackInfo info)
\r
2874 mediaControllerAudioInfoChanged (host, info);
\r
2878 public void onMetadataChanged (MediaMetadata metadata)
\r
2880 mediaControllerMetadataChanged (host, metadata);
\r
2884 public void onPlaybackStateChanged (PlaybackState state)
\r
2886 mediaControllerPlaybackStateChanged (host, state);
\r
2890 public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
\r
2893 public void onSessionDestroyed()
\r
2895 mediaControllerSessionDestroyed (host);
\r
2898 private long host;
\r
2901 //==============================================================================
\r
2902 public class MediaSessionCallback extends MediaSession.Callback
\r
2904 private native void mediaSessionPause (long host);
\r
2905 private native void mediaSessionPlay (long host);
\r
2906 private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
\r
2907 private native void mediaSessionSeekTo (long host, long pos);
\r
2908 private native void mediaSessionStop (long host);
\r
2911 MediaSessionCallback (long hostToUse)
\r
2917 public void onPause()
\r
2919 mediaSessionPause (host);
\r
2923 public void onPlay()
\r
2925 mediaSessionPlay (host);
\r
2929 public void onPlayFromMediaId (String mediaId, Bundle extras)
\r
2931 mediaSessionPlayFromMediaId (host, mediaId, extras);
\r
2935 public void onSeekTo (long pos)
\r
2937 mediaSessionSeekTo (host, pos);
\r
2941 public void onStop()
\r
2943 mediaSessionStop (host);
\r
2947 public void onFastForward() {}
\r
2950 public boolean onMediaButtonEvent (Intent mediaButtonIntent)
\r
2956 public void onRewind() {}
\r
2959 public void onSkipToNext() {}
\r
2962 public void onSkipToPrevious() {}
\r
2965 public void onSkipToQueueItem (long id) {}
\r
2967 private long host;
\r
2970 //==============================================================================
\r
2971 public class SystemVolumeObserver extends ContentObserver
\r
2973 private native void mediaSessionSystemVolumeChanged (long host);
\r
2975 SystemVolumeObserver (Activity activityToUse, long hostToUse)
\r
2979 activity = activityToUse;
\r
2983 void setEnabled (boolean shouldBeEnabled)
\r
2985 if (shouldBeEnabled)
\r
2986 activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
\r
2988 activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
\r
2992 public void onChange (boolean selfChange, Uri uri)
\r
2994 if (uri.toString().startsWith ("content://settings/system/volume_music"))
\r
2995 mediaSessionSystemVolumeChanged (host);
\r
2998 private Activity activity;
\r
2999 private long host;
\r
3003 //==============================================================================
\r
3004 public static final String getLocaleValue (boolean isRegion)
\r
3006 java.util.Locale locale = java.util.Locale.getDefault();
\r
3008 return isRegion ? locale.getCountry()
\r
3009 : locale.getLanguage();
\r
3012 private static final String getFileLocation (String type)
\r
3014 return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
\r
3017 public static final String getDocumentsFolder()
\r
3019 if (getAndroidSDKVersion() >= 19)
\r
3020 return getFileLocation ("Documents");
\r
3022 return Environment.getDataDirectory().getAbsolutePath();
\r
3025 public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
\r
3026 public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
\r
3027 public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
\r
3028 public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
\r
3030 //==============================================================================
\r
3032 protected void onActivityResult (int requestCode, int resultCode, Intent data)
\r
3034 appActivityResult (requestCode, resultCode, data);
\r
3038 protected void onNewIntent (Intent intent)
\r
3040 super.onNewIntent(intent);
\r
3041 setIntent(intent);
\r
3043 appNewIntent (intent);
\r
3046 //==============================================================================
\r
3047 public final Typeface getTypeFaceFromAsset (String assetName)
\r
3051 return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
\r
3053 catch (Throwable e) {}
\r
3058 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
\r
3060 public static String bytesToHex (byte[] bytes)
\r
3062 char[] hexChars = new char[bytes.length * 2];
\r
3064 for (int j = 0; j < bytes.length; ++j)
\r
3066 int v = bytes[j] & 0xff;
\r
3067 hexChars[j * 2] = hexArray[v >>> 4];
\r
3068 hexChars[j * 2 + 1] = hexArray[v & 0x0f];
\r
3071 return new String (hexChars);
\r
3074 final private java.util.Map dataCache = new java.util.HashMap();
\r
3076 synchronized private final File getDataCacheFile (byte[] data)
\r
3080 java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
\r
3081 digest.update (data);
\r
3083 String key = bytesToHex (digest.digest());
\r
3085 if (dataCache.containsKey (key))
\r
3086 return (File) dataCache.get (key);
\r
3088 File f = new File (this.getCacheDir(), "bindata_" + key);
\r
3090 FileOutputStream os = new FileOutputStream (f);
\r
3091 os.write (data, 0, data.length);
\r
3092 dataCache.put (key, f);
\r
3095 catch (Throwable e) {}
\r
3100 private final void clearDataCache()
\r
3102 java.util.Iterator it = dataCache.values().iterator();
\r
3104 while (it.hasNext())
\r
3106 File f = (File) it.next();
\r
3111 public final Typeface getTypeFaceFromByteArray (byte[] data)
\r
3115 File f = getDataCacheFile (data);
\r
3118 return Typeface.createFromFile (f);
\r
3120 catch (Exception e)
\r
3122 Log.e ("JUCE", e.toString());
\r
3128 public static final int getAndroidSDKVersion()
\r
3130 return android.os.Build.VERSION.SDK_INT;
\r
3133 public final String audioManagerGetProperty (String property)
\r
3135 Object obj = getSystemService (AUDIO_SERVICE);
\r
3139 java.lang.reflect.Method method;
\r
3143 method = obj.getClass().getMethod ("getProperty", String.class);
\r
3145 catch (SecurityException e) { return null; }
\r
3146 catch (NoSuchMethodException e) { return null; }
\r
3148 if (method == null)
\r
3153 return (String) method.invoke (obj, property);
\r
3155 catch (java.lang.IllegalArgumentException e) {}
\r
3156 catch (java.lang.IllegalAccessException e) {}
\r
3157 catch (java.lang.reflect.InvocationTargetException e) {}
\r
3162 public final boolean hasSystemFeature (String property)
\r
3164 return getPackageManager().hasSystemFeature (property);
\r